// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef UI_BASE_X_SELECTION_REQUESTOR_H_
#define UI_BASE_X_SELECTION_REQUESTOR_H_

#include <stddef.h>

#include <vector>

#include "base/callback.h"
#include "base/event_types.h"
#include "base/macros.h"
#include "base/memory/ref_counted_memory.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "ui/base/ui_base_export.h"
#include "ui/gfx/x/x11_atom_cache.h"
#include "ui/gfx/x/x11_types.h"

namespace ui {
class PlatformEventDispatcher;
class SelectionData;

// Requests and later receives data from the X11 server through the selection
// system.
//
// X11 uses a system called "selections" to implement clipboards and drag and
// drop. This class interprets messages from the stateful selection request
// API. SelectionRequestor should only deal with the X11 details; it does not
// implement per-component fast-paths.
class UI_BASE_EXPORT SelectionRequestor {
public:
    SelectionRequestor(XDisplay* xdisplay,
        XID xwindow,
        PlatformEventDispatcher* dispatcher);
    ~SelectionRequestor();

    // Does the work of requesting |target| from |selection|, spinning up the
    // nested message loop, and reading the resulting data back. The result is
    // stored in |out_data|.
    // |out_data_items| is the length of |out_data| in |out_type| items.
    bool PerformBlockingConvertSelection(
        XAtom selection,
        XAtom target,
        scoped_refptr<base::RefCountedMemory>* out_data,
        size_t* out_data_items,
        XAtom* out_type);

    // Requests |target| from |selection|, passing |parameter| as a parameter to
    // XConvertSelection().
    void PerformBlockingConvertSelectionWithParameter(
        XAtom selection,
        XAtom target,
        const std::vector<XAtom>& parameter);

    // Returns the first of |types| offered by the current owner of |selection|.
    // Returns an empty SelectionData object if none of |types| are available.
    SelectionData RequestAndWaitForTypes(XAtom selection,
        const std::vector<XAtom>& types);

    // It is our owner's responsibility to plumb X11 SelectionNotify events on
    // |xwindow_| to us.
    void OnSelectionNotify(const XEvent& event);

    // Returns true if SelectionOwner can process the XChangeProperty event,
    // |event|.
    bool CanDispatchPropertyEvent(const XEvent& event);

    void OnPropertyEvent(const XEvent& event);

private:
    friend class SelectionRequestorTest;

    // A request that has been issued.
    struct Request {
        Request(XAtom selection, XAtom target, base::TimeTicks timeout);
        ~Request();

        // The target and selection requested in the XConvertSelection() request.
        // Used for error detection.
        XAtom selection;
        XAtom target;

        // Whether the result of the XConvertSelection() request is being sent
        // incrementally.
        bool data_sent_incrementally;

        // The result data for the XConvertSelection() request.
        std::vector<scoped_refptr<base::RefCountedMemory>> out_data;
        size_t out_data_items;
        XAtom out_type;

        // Whether the XConvertSelection() request was successful.
        bool success;

        // The time when the request should be aborted.
        base::TimeTicks timeout;

        // Called to terminate the nested message loop.
        base::Closure quit_closure;

        // True if the request is complete.
        bool completed;
    };

    // Aborts requests which have timed out.
    void AbortStaleRequests();

    // Mark |request| as completed. If the current request is completed, converts
    // the selection for the next request.
    void CompleteRequest(size_t index, bool success);

    // Converts the selection for the request at |current_request_index_|.
    void ConvertSelectionForCurrentRequest();

    // Blocks till SelectionNotify is received for the target specified in
    // |request|.
    void BlockTillSelectionNotifyForRequest(Request* request);

    // Returns the request at |current_request_index_| or NULL if there isn't any.
    Request* GetCurrentRequest();

    // Our X11 state.
    XDisplay* x_display_;
    XID x_window_;

    // The property on |x_window_| set by the selection owner with the value of
    // the selection.
    XAtom x_property_;

    // Dispatcher which handles SelectionNotify and SelectionRequest for
    // |selection_name_|. PerformBlockingConvertSelection() calls the
    // dispatcher directly if PerformBlockingConvertSelection() is called after
    // the PlatformEventSource is destroyed.
    // Not owned.
    PlatformEventDispatcher* dispatcher_;

    // In progress requests. Requests are added to the list at the start of
    // PerformBlockingConvertSelection() and are removed and destroyed right
    // before the method terminates.
    std::vector<Request*> requests_;

    // The index of the currently active request in |requests_|. The active
    // request is the request for which XConvertSelection() has been
    // called and for which we are waiting for a SelectionNotify response.
    size_t current_request_index_;

    // Used to abort requests if the selection owner takes too long to respond.
    base::RepeatingTimer abort_timer_;

    X11AtomCache atom_cache_;

    DISALLOW_COPY_AND_ASSIGN(SelectionRequestor);
};

} // namespace ui

#endif // UI_BASE_X_SELECTION_REQUESTOR_H_
